module cache(
    input          clk      ,
    input          resetn   ,
    // cpu
    input          valid    ,
    input          mtype    ,
    input          op       , 
    input  [  7:0] index    , 
    input  [ 19:0] tag      , 
    input  [  3:0] offset   ,

    input  [  3:0] wstrb    ,
    input  [ 31:0] wdata    ,
    output         addr_ok  ,
    output         data_ok  ,
    output [ 31:0] rdata    ,
    // axi r
    output         rd_req   ,
    output [  2:0] rd_type  , 
    output [ 31:0] rd_addr  ,
    input          rd_rdy   ,
    input          ret_valid,
    input          ret_last ,
    input  [ 31:0] ret_data ,
    // axi w
    output         wr_req   ,
    output [  2:0] wr_type  ,
    output [ 31:0] wr_addr  ,
    output [  3:0] wr_wstrb ,
    output [127:0] wr_data  ,
    input          wr_rdy   );
/************************ DECLEARATION ************************/
    //mtype
    wire write_through;
    //request buffer
    reg           reg_op;
    reg   [ 7:0]  reg_index;
    reg   [19:0]  reg_tag;
    reg   [ 3:0]  reg_offset;
    reg           reg_mtype;

    //tag compare
    wire  way0_hit;
    wire  way1_hit;
    wire  cache_hit;
    wire  [19:0]  way0_tag;
    wire  [19:0]  way1_tag;
    wire  way0_v;
    wire  way1_v;

    //data select
    wire  [127:0] way0_data;
    wire  [127:0] way1_data;    
    wire  [31:0]  way0_load_word;
    wire  [31:0]  way1_load_word;
    wire  [31:0]  load_res;
    wire  [127:0] replace_data;
    wire          replace_way;
    
    //LFSR
    reg           lfsr;
    
    //write buffer
    wire         hit_write;
    reg          reg_way;
    reg   [ 3:0] reg_wstrb;
    reg   [31:0] reg_wdata;
    reg   [ 3:0] reg_reg_offset;
    reg   [ 3:0] reg_reg_wstrb;
    reg   [31:0] reg_reg_wdata;
    reg   [ 7:0] reg_reg_index;
    wire  [ 1:0] reg_bank;

    //read after write
    wire rd_after_wr;
    
    //state machine
    reg [4:0] main_state;
    reg [4:0] main_nstate;
    reg [1:0] write_state;
    reg [1:0] write_nstate;
    parameter IDLE    = 5'd1;
    parameter LOOKUP  = 5'd2;
    parameter MISS    = 5'd4;
    parameter REPLACE = 5'd8;
    parameter REFILL  = 5'd16;
    parameter WRITE   = 2'd2;
    
    reg reg_wr_req;
    //miss buffer
    reg [1:0]  ret_bank;
    reg [127:0] refill_res;
    //dirty
    reg [255:0] dirty_way0;
    reg [255:0] dirty_way1;
    
    //ram
    wire        write_way0_databank0;
    wire        write_way0_databank1;
    wire        write_way0_databank2;
    wire        write_way0_databank3;
    wire        write_way1_databank0;
    wire        write_way1_databank1;
    wire        write_way1_databank2;
    wire        write_way1_databank3;
    wire        refill_way0_databank0;
    wire        refill_way0_databank1;
    wire        refill_way0_databank2;
    wire        refill_way0_databank3;
    wire        refill_way1_databank0;
    wire        refill_way1_databank1;
    wire        refill_way1_databank2;
    wire        refill_way1_databank3;
    
    wire        way0_tagv_en;
    wire [ 7:0] way0_tagv_addr;
    wire [20:0] way0_tagv_wdata;
    wire [20:0] way0_tagv_rdata;
    wire [ 3:0] way0_databank0_we;
    wire [ 7:0] way0_databank0_addr;
    wire [31:0] way0_databank0_wdata;
    wire [31:0] way0_databank0_rdata;
    wire [ 3:0] way0_databank1_we;
    wire [ 7:0] way0_databank1_addr;
    wire [31:0] way0_databank1_wdata;
    wire [31:0] way0_databank1_rdata;
    wire [ 3:0] way0_databank2_we;
    wire [ 7:0] way0_databank2_addr;
    wire [31:0] way0_databank2_wdata;
    wire [31:0] way0_databank2_rdata;
    wire [ 3:0] way0_databank3_we;
    wire [ 7:0] way0_databank3_addr;
    wire [31:0] way0_databank3_wdata;
    wire [31:0] way0_databank3_rdata;
    
    wire        way1_tagv_en;
    wire [ 7:0] way1_tagv_addr;
    wire [20:0] way1_tagv_wdata;
    wire [20:0] way1_tagv_rdata;
    wire [ 3:0] way1_databank0_we;
    wire [ 7:0] way1_databank0_addr;
    wire [31:0] way1_databank0_wdata;
    wire [31:0] way1_databank0_rdata;
    wire [ 3:0] way1_databank1_we;
    wire [ 7:0] way1_databank1_addr;
    wire [31:0] way1_databank1_wdata;
    wire [31:0] way1_databank1_rdata;
    wire [ 3:0] way1_databank2_we;
    wire [ 7:0] way1_databank2_addr;
    wire [31:0] way1_databank2_wdata;
    wire [31:0] way1_databank2_rdata;
    wire [ 3:0] way1_databank3_we;
    wire [ 7:0] way1_databank3_addr;
    wire [31:0] way1_databank3_wdata;
    wire [31:0] way1_databank3_rdata;
/************************ SEQUENTIAL LOGIC ************************/
    //request buffer
    always@(posedge clk) begin
        if (!resetn) begin
            reg_op <= 0;
            reg_index <= 0;
            reg_tag <= 0;
            reg_offset <= 0;
            //for write buffer
            reg_wstrb <= 0;
            reg_wdata <= 0;
            reg_mtype <= 0;
        end
        else if ((valid & main_state==IDLE & addr_ok )|(main_state==LOOKUP & valid & cache_hit)) begin
            reg_op <= op;
            reg_index <= index;
            reg_tag <= tag;
            reg_offset <= offset;
            reg_wstrb <= wstrb;
            reg_wdata <= wdata;
            reg_mtype <= mtype;
        end
    end
    //LFSR
    always@(posedge clk) begin
    if(main_state==MISS)
        lfsr = $random % 2;
    end

    //write buffer
    assign hit_write = main_state==LOOKUP && cache_hit && reg_op==1'b1 && reg_mtype==1'b1;
    always @(posedge clk) begin
        if (!resetn) begin
            reg_reg_index <= 0;
            reg_way <= 0;
            reg_reg_wstrb <= 0;
            reg_reg_wdata <= 0;
            reg_reg_offset <= 0;
        end
        else if (hit_write) begin
            reg_reg_index <= reg_index;
            reg_way <= way1_hit;
            reg_reg_wstrb <= reg_wstrb;
            reg_reg_wdata <= reg_wdata;
            reg_reg_offset <= reg_offset;
        end
    end

    //dirty
    always @(posedge clk) begin
        if (!resetn) begin
            dirty_way0 <= 255'd0;
            dirty_way1 <= 255'd0;
        end
        else if (write_state==WRITE) begin
            if (reg_way) begin
                dirty_way1[reg_reg_index] <= 1'b1;
            end
            else begin
                dirty_way0[reg_reg_index] <= 1'b1;
            end
        end
        else if (main_state==REFILL & ret_valid & ret_last) begin
            if (replace_way) begin
                if(reg_op) begin
                    dirty_way1[reg_index] <= 1'b1;
                end
                else begin
                    dirty_way1[reg_index] <= 1'b0;
                end
            end
            else begin
                if(reg_op) begin
                    dirty_way0[reg_index] <= 1'b1;
                end
                else begin
                    dirty_way0[reg_index] <= 1'b0;
                end
            end
        end
    end

    //reg for wr_req
    always @(posedge clk) begin
        if(!resetn)
            reg_wr_req <= 0;
        else if (main_state==LOOKUP && ~cache_hit)
            reg_wr_req <= 1'b1;
        else if (reg_wr_req)
            reg_wr_req <= 0;
    end

    // miss buffer
    assign replace_way = lfsr;
    always @(posedge clk) begin
        if (main_state==REPLACE && rd_rdy==1'b1)
            ret_bank <= 0;
        else if (ret_valid)
            ret_bank <= ret_bank+1;
    end
    //state machine
    always@(posedge clk) begin
            if (!resetn) begin
                main_state <= IDLE;
            end
            else begin
                main_state <= main_nstate;
            end
    end
    always@(*) begin
        case(main_state)
            IDLE: begin
                if (rd_after_wr)
                    main_nstate = IDLE;
                else if (valid)
                    main_nstate = LOOKUP;
                else
                    main_nstate = IDLE;
            end

            LOOKUP:begin
                if (rd_after_wr)
                    main_nstate = IDLE;
                if (~cache_hit | write_through)
                    main_nstate = MISS;
                else if (valid) 
                    main_nstate = LOOKUP;
                else
                    main_nstate = IDLE;
            end

            MISS:begin
                if ((wr_rdy && wr_req) || !reg_wr_req) begin
                    main_nstate = REPLACE;
                end
                else
                    main_nstate = MISS;
            end

            REPLACE:begin
                if (rd_rdy | write_through)
                    main_nstate = REFILL;
                else
                    main_nstate = REPLACE;
            end

            REFILL:begin
                if ((ret_last & ret_valid) | write_through)
                    main_nstate = IDLE;
                else
                    main_nstate = REFILL;
            end

            default:
                main_nstate = IDLE;
        endcase
    end

    always@(posedge clk) begin
        if (!resetn) begin
            write_state <= IDLE;
        end
        else begin
            write_state <= write_nstate;
        end
    end
    always @(*) begin
        case(write_state)
            IDLE:begin
                if(hit_write)
                    write_nstate <= WRITE;
                else begin
                    write_nstate <= IDLE;
                end
            end
            WRITE:begin
                if(hit_write)
                    write_nstate <= WRITE;
                else begin
                    write_nstate <= IDLE;
                end
            end
        endcase
    end
/************************ COMBINATIONAL LOGIC ************************/
//MTYPE
    assign write_through = (reg_op == 1'b1) && (reg_mtype == 1'b0);
//TO CPU
    //cpu output
    assign rdata  =  ret_valid ? ret_data : load_res;
    assign addr_ok = (main_state == IDLE) || ((main_state==LOOKUP) && (main_nstate==LOOKUP));
    assign data_ok = (cache_hit & (main_state == LOOKUP)) |
                    ((main_state==REFILL)& ret_valid & (ret_bank==reg_offset[3:2]));
//LOOKUP
    //rd_after_wr
    assign rd_after_wr = (main_state==LOOKUP & reg_op==1'b1 & valid & op==1'b0 & offset[3:2]==reg_offset[3:2]) |
                    (write_state==WRITE & valid==1'b1 & op==1'b0 & offset[3:2]==reg_offset[3:2]);

    //tag compare
    assign way0_hit = way0_v && (way0_tag == reg_tag);
    assign way1_hit = way1_v && (way1_tag == reg_tag);
    assign cache_hit = way0_hit || way1_hit;

    //data_select
    assign way0_data = {way0_databank3_rdata,way0_databank2_rdata,way0_databank1_rdata,way0_databank0_rdata};
    assign way1_data = {way1_databank3_rdata,way1_databank2_rdata,way1_databank1_rdata,way1_databank0_rdata};
    assign way0_load_word = {32{reg_offset[3:2]==2'd0}} & way0_data[31:0]
                         |  {32{reg_offset[3:2]==2'd1}} & way0_data[63:32]
                         |  {32{reg_offset[3:2]==2'd2}} & way0_data[95:64]
                         |  {32{reg_offset[3:2]==2'd3}} & way0_data[127:96];
    assign way1_load_word = {32{reg_offset[3:2]==2'd0}} & way1_data[31:0]
                         |  {32{reg_offset[3:2]==2'd1}} & way1_data[63:32]
                         |  {32{reg_offset[3:2]==2'd2}} & way1_data[95:64]
                         |  {32{reg_offset[3:2]==2'd3}} & way1_data[127:96];
    assign load_res = {32{way0_hit}} & way0_load_word |
                    {32{way1_hit}} & way1_load_word;
    assign replace_data = replace_way ? way1_data : way0_data;
//WRITE
    //wire connect to reg
    assign reg_bank = reg_reg_offset[3:2];
    //write bank(bank select)
    assign write_way0_databank0 = valid & (write_state==WRITE) &(reg_way==1'd0) & (reg_bank==2'b0);
    assign write_way0_databank1 = valid & (write_state==WRITE) &(reg_way==1'd0) & (reg_bank==2'd1);
    assign write_way0_databank2 = valid & (write_state==WRITE) &(reg_way==1'd0) & (reg_bank==2'd2);
    assign write_way0_databank3 = valid & (write_state==WRITE) &(reg_way==1'd0) & (reg_bank==2'd3);
    assign write_way1_databank0 = valid & (write_state==WRITE) &(reg_way==1'd1) & (reg_bank==2'b0);
    assign write_way1_databank1 = valid & (write_state==WRITE) &(reg_way==1'd1) & (reg_bank==2'd1);
    assign write_way1_databank2 = valid & (write_state==WRITE) &(reg_way==1'd1) & (reg_bank==2'd2);
    assign write_way1_databank3 = valid & (write_state==WRITE) &(reg_way==1'd1) & (reg_bank==2'd3);

//MISS
    //axi output
    assign wr_req = reg_wr_req & ((replace_way ? (way1_v & dirty_way1[reg_index]) : (way0_v & dirty_way0[reg_index]))
                                || write_through);
    assign wr_type = write_through ? 3'b010 : 3'b100;
    assign wr_addr = write_through ? {reg_tag, reg_index, reg_offset} :
            {((replace_way)? way1_tag : way0_tag),reg_index,reg_offset};
    assign wr_wstrb = 4'b1111;
    assign wr_data = write_through ? {96'b0, reg_wdata} : replace_data;

//REPLACE
    assign rd_req = main_state == REPLACE && !write_through;
    assign rd_type = 3'b100;
    assign rd_addr = {reg_tag,reg_index,4'b0};

//REFILL
    //refill data(bank select)
    assign refill_way0_databank0 = ret_valid & (main_state==REFILL) & (ret_bank==2'd0) 
                                & (replace_way==1'b0) & ~write_through;
    assign refill_way0_databank1 = ret_valid & (main_state==REFILL) & (ret_bank==2'd1)
                                & (replace_way==1'b0) & ~write_through;
    assign refill_way0_databank2 = ret_valid & (main_state==REFILL) & (ret_bank==2'd2)
                                & (replace_way==1'b0) & ~write_through;
    assign refill_way0_databank3 = ret_valid & (main_state==REFILL) & (ret_bank==2'd3)
                                & (replace_way==1'b0) & ~write_through;
    assign refill_way1_databank0 = ret_valid & (main_state==REFILL) & (ret_bank==2'd0)
                                & (replace_way==1'b1) & ~write_through;
    assign refill_way1_databank1 = ret_valid & (main_state==REFILL) & (ret_bank==2'd1)
                                & (replace_way==1'b1) & ~write_through;
    assign refill_way1_databank2 = ret_valid & (main_state==REFILL) & (ret_bank==2'd2)
                                & (replace_way==1'b1) & ~write_through;
    assign refill_way1_databank3 = ret_valid & (main_state==REFILL) & (ret_bank==2'd3)
                                & (replace_way==1'b1) & ~write_through;
    //ram_wstrb
    assign way0_databank0_we=  ({4{write_way0_databank0}}& reg_reg_wstrb) |
                            {4{refill_way0_databank0}};  
    assign way0_databank1_we=  ({4{write_way0_databank1}}& reg_reg_wstrb) |
                            {4{refill_way0_databank1}};  
    assign way0_databank2_we=  ({4{write_way0_databank2}}& reg_reg_wstrb) |
                            {4{refill_way0_databank2}};  
    assign way0_databank3_we=  ({4{write_way0_databank3}}& reg_reg_wstrb) | 
                            {4{refill_way0_databank3}};   
    assign way1_databank0_we=  ({4{write_way1_databank0}}& reg_reg_wstrb) | 
                            {4{refill_way1_databank0}};  
    assign way1_databank1_we=  ({4{write_way1_databank1}}& reg_reg_wstrb) | 
                            {4{refill_way1_databank1}};  
    assign way1_databank2_we=  ({4{write_way1_databank2}}& reg_reg_wstrb) | 
                            {4{refill_way1_databank2}};  
    assign way1_databank3_we=  ({4{write_way1_databank3}}& reg_reg_wstrb) | 
                            {4{refill_way1_databank3}};  
//INDEX
    //ram_addr
    assign way0_databank0_addr = ({8{((main_state==IDLE) | (main_state==LOOKUP))}} & index) |
                                ({8{((main_state==MISS) | (main_state==REFILL))}} & reg_index) |
                                ({8{write_way0_databank0}} & reg_reg_index);
    assign way0_databank1_addr = ({8{((main_state==IDLE) | (main_state==LOOKUP))}} & index) |
                                ({8{((main_state==MISS) | (main_state==REFILL))}} & reg_index) |
                                ({8{write_way0_databank1}} & reg_reg_index);
    assign way0_databank2_addr = ({8{((main_state==IDLE) | (main_state==LOOKUP))}} & index) |
                                ({8{((main_state==MISS) | (main_state==REFILL))}} & reg_index) |
                                ({8{write_way0_databank2}} & reg_reg_index);
    assign way0_databank3_addr = ({8{((main_state==IDLE) | (main_state==LOOKUP))}} & index) |
                                ({8{((main_state==MISS) | (main_state==REFILL))}} & reg_index) |
                                ({8{write_way0_databank3}} & reg_reg_index);
    assign way1_databank0_addr = ({8{((main_state==IDLE) | (main_state==LOOKUP))}} & index) |
                                ({8{((main_state==MISS) | (main_state==REFILL))}} & reg_index) |
                                ({8{write_way1_databank0}} & reg_reg_index);
    assign way1_databank1_addr = ({8{((main_state==IDLE) | (main_state==LOOKUP))}} & index) |
                                ({8{((main_state==MISS) | (main_state==REFILL))}} & reg_index) |
                                ({8{write_way1_databank1}} & reg_reg_index);
    assign way1_databank2_addr = ({8{((main_state==IDLE) | (main_state==LOOKUP))}} & index) |
                                ({8{((main_state==MISS) | (main_state==REFILL))}} & reg_index) |
                                ({8{write_way1_databank2}} & reg_reg_index);
    assign way1_databank3_addr = ({8{((main_state==IDLE) | (main_state==LOOKUP))}} & index) |
                                ({8{((main_state==MISS) | (main_state==REFILL))}} & reg_index) |
                                ({8{write_way1_databank3}} & reg_reg_index);
//WRITE DATA
    //ram_wdata
    assign way0_databank0_wdata = ({32{write_way0_databank0}} & reg_reg_wdata) |
                                ({32{refill_way0_databank0}} & ((reg_op & reg_offset[3:2]==2'd0) ? reg_wdata : ret_data));
    assign way0_databank1_wdata = ({32{write_way0_databank1}} & reg_reg_wdata) |
                                ({32{refill_way0_databank1}} & ((reg_op & reg_offset[3:2]==2'd1) ? reg_wdata : ret_data));
    assign way0_databank2_wdata = ({32{write_way0_databank2}} & reg_reg_wdata) |
                                ({32{refill_way0_databank2}} & ((reg_op & reg_offset[3:2]==2'd2) ? reg_wdata : ret_data));
    assign way0_databank3_wdata = ({32{write_way0_databank3}} & reg_reg_wdata) |
                                ({32{refill_way0_databank3}} & ((reg_op & reg_offset[3:2]==2'd3) ? reg_wdata : ret_data));
    assign way1_databank0_wdata = ({32{write_way1_databank0}} & reg_reg_wdata) |
                                ({32{refill_way1_databank0}} & ((reg_op & reg_offset[3:2]==2'd0) ? reg_wdata : ret_data));
    assign way1_databank1_wdata = ({32{write_way1_databank1}} & reg_reg_wdata) |
                                ({32{refill_way1_databank1}} & ((reg_op & reg_offset[3:2]==2'd1) ? reg_wdata : ret_data));
    assign way1_databank2_wdata = ({32{write_way1_databank2}} & reg_reg_wdata) |
                                ({32{refill_way1_databank2}} & ((reg_op & reg_offset[3:2]==2'd2) ? reg_wdata : ret_data));
    assign way1_databank3_wdata = ({32{write_way1_databank3}} & reg_reg_wdata) |
                                ({32{refill_way1_databank3}} & ((reg_op & reg_offset[3:2]==2'd3) ? reg_wdata : ret_data));
//TAGV
    //tagv
    assign way0_tagv_en = main_state==REFILL & ret_valid & ret_last & ~replace_way & ~write_through; 
    assign way0_tagv_addr = ((main_state==REFILL) | (main_state==MISS)) ? reg_index : index;  //REFILL for write,other for read
    assign way0_tagv_wdata = {reg_tag,1'b1};
    assign way0_v = way0_tagv_rdata[0];
    assign way0_tag = way0_tagv_rdata[20:1];
    assign way1_tagv_en = main_state==REFILL & ret_valid & ret_last & replace_way & ~write_through;
    assign way1_tagv_addr = ((main_state==REFILL) | (main_state==MISS)) ? reg_index : index;
    assign way1_tagv_wdata = {reg_tag,1'b1};
    assign way1_v = way1_tagv_rdata[0];
    assign way1_tag = way1_tagv_rdata[20:1];
/************************ RAM INSTANCE ************************/
    //module ram
    databank_ram way0_databank0(
        .clka(clk),
        //.ena(way0_databank0_en),
        .wea(way0_databank0_we),
        .addra(way0_databank0_addr),
        .dina(way0_databank0_wdata),
        .douta(way0_databank0_rdata)
        );
    databank_ram way0_databank1(
        .clka(clk),
        //.ena(way0_databank1_en),
        .wea(way0_databank1_we),
        .addra(way0_databank1_addr),
        .dina(way0_databank1_wdata),
        .douta(way0_databank1_rdata)
        );
    databank_ram way0_databank2(
        .clka(clk),
        //.ena(way0_databank2_en),
        .wea(way0_databank2_we),
        .addra(way0_databank2_addr),
        .dina(way0_databank2_wdata),
        .douta(way0_databank2_rdata)
        );
    databank_ram way0_databank3(
        .clka(clk),
        //.ena(way0_databank3_en),
        .wea(way0_databank3_we),
        .addra(way0_databank3_addr),
        .dina(way0_databank3_wdata),
        .douta(way0_databank3_rdata)
        );
    databank_ram way1_databank0(
        .clka(clk),
        //.ena(way1_databank0_en),
        .wea(way1_databank0_we),
        .addra(way1_databank0_addr),
        .dina(way1_databank0_wdata),
        .douta(way1_databank0_rdata)
        );
    databank_ram way1_databank1(
        .clka(clk),
        //.ena(way1_databank1_en),
        .wea(way1_databank1_we),
        .addra(way1_databank1_addr),
        .dina(way1_databank1_wdata),
        .douta(way1_databank1_rdata)
        );
    databank_ram way1_databank2(
        .clka(clk),
        //.ena(way1_databank2_en),
        .wea(way1_databank2_we),
        .addra(way1_databank2_addr),
        .dina(way1_databank2_wdata),
        .douta(way1_databank2_rdata)
        );
    databank_ram way1_databank3(
        .clka(clk),
        //.ena(way1_databank3_en),
        .wea(way1_databank3_we),
        .addra(way1_databank3_addr),
        .dina(way1_databank3_wdata),
        .douta(way1_databank3_rdata)
        );
    tagv_ram way0_tagv(
        .clka(clk),
        .wea(way0_tagv_en),
        //.ena(1'b0),
        .addra(way0_tagv_addr),
        .dina(way0_tagv_wdata),
        .douta(way0_tagv_rdata)
        );
    tagv_ram way1_tagv(
        .clka(clk),
        .wea(way1_tagv_en),
        //.ena(1'b0),
        .addra(way1_tagv_addr),
        .dina(way1_tagv_wdata),
        .douta(way1_tagv_rdata)
        );
endmodule